package com.inspinia.message.common.util;

import com.inspinia.base.heartbeat.util.BrowserHeartbeatUtil;
import com.inspinia.base.util.JsonUtils;
import com.inspinia.message.common.model.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.concurrent.ConcurrentLinkedDeque;

/**
 * websocket 具体业务处理方法
 * @author Veblen
 */

@Component
@Sharable
@Slf4j
public class WebSocketServerHandler extends BaseWebSocketServerHandler {

    /**
     * 自定义事件处理器
     */
    private WebSocketServerHandshaker handshaker;


    /**
     * 当客户端连接成功，返回成功信息
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // TODO Auto-generated method stub
        log.info("客户端连接成功");
    }

    /**
     * 当客户端断开连接
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        removeFromUerIdSessionMap(ctx);
        log.info("有一链接关闭，当前链接数为：" + MessageConstant.pushCtxMap.size());
    }

    /**
     * 移除方法
     * @param ctx 通道
     */
    private void removeFromUerIdSessionMap(ChannelHandlerContext ctx) {
        //遍历
        for (String uid : MessageConstant.pushCtxMap.keySet()) {
            ConcurrentLinkedDeque<ChannelHandlerContext> ws = MessageConstant.pushCtxMap.get(uid);
            if (ws != null) {
                for (ChannelHandlerContext w : ws) {
                    if (w == ctx) {
                        ws.remove(w);
                        log.info("有一链接关闭，用户id是：" + uid + "，当前链接数为：" + MessageConstant.pushCtxMap.size());
                    }
                }
                if (ws.isEmpty()) {
                    MessageConstant.pushCtxMap.remove(uid);
                    BrowserHeartbeatUtil.lostAllUserHeartbeat(Long.valueOf(uid));
                }
            }
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // TODO Auto-generated method stub
        ctx.flush();
    }


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        // TODO Auto-generated method stub

        //http：//xxxx
        if (msg instanceof FullHttpRequest) {

            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {
            //ws://xxxx
            handlerWebSocketFrame(ctx, (WebSocketFrame) msg);
        }


    }

    /**
     * 处理消息
     * @param ctx
     * @param frame
     * @throws Exception
     */
    public void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
        //关闭请求
        if (frame instanceof CloseWebSocketFrame) {
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            return;
        }
        //ping请求
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        //只支持文本格式，不支持二进制消息
        if (!(frame instanceof TextWebSocketFrame)) {
            log.info("仅支持文本格式");
        }
        //客服端发送过来的消息
        String request = ((TextWebSocketFrame) frame).text();
        System.out.println("服务端收到：" + request);

    }

    /**
     * 给所有客户端群发消息
     *
     * @param message 消息内容
     */
    public static void sendMsgToAll(String message) {
        //取出所有通道推送消息
        push(MessageConstant.ChannelGroup,message);
    }

    /**
     * 推送消息到已加入通道的客户端
     * @param message
     * @param userId
     */
    public static void sendMessage(Long userId,final String message) {
        MessageConstant.pushCtxMap.forEach((k, deque) -> {
            if (Long.valueOf(k).equals(userId)){
                if (deque != null) {
                    for (ChannelHandlerContext ws : deque) {
                        push(ws,message);
                    }
                }
            }
        });
    }

    /**
     * 第一次请求是http请求，请求头包括ws的信息
     * @param ctx
     * @param req
     */
    public void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
        if (!req.decoderResult().isSuccess()) {
            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws:/" + ctx.channel() + "/websocket", null, false);
        handshaker = wsFactory.newHandshaker(req);
        if (handshaker == null) {
            //不支持
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            //保存用户id和ctx对应关系
            String uri = req.uri();
            String uid = uri.substring(req.getUri().indexOf("=") + 1, uri.length());
            saveToUserIdChannelMap(uid,ctx);
            try {
                //设置心跳
                BrowserHeartbeatUtil.setFirstGetHeartbeat(Long.valueOf(uid));
                handshaker.handshake(ctx.channel(), req);
                log.info("有新链接加入，用户id是：" + uid + "当前总连接数为：" + MessageConstant.pushCtxMap.size() + ",该用户连接数为" + MessageConstant.pushCtxMap.get(uid).size());
            } catch (Exception e) {
                sendBusinessExceptionMessage(Long.valueOf(uid), e);
            }
        }

    }

    /**
     * 发送业务异常信息
     * @param userId
     * @param e
     */
    private void sendBusinessExceptionMessage(Long userId, Exception e) {
        Notice notice = new Notice();
        notice.setType(NoticeType.sys);
        notice.setLinkType(NoticeLinkType.businessException);
        notice.setContent(e.getMessage());
        sendMessage(userId, JsonUtils.toJson(notice));
    }

    /**
     * 保存用户通道
     * @param userId
     * @param ctx
     */
    private void saveToUserIdChannelMap(String userId, ChannelHandlerContext ctx) {
        ConcurrentLinkedDeque<ChannelHandlerContext> deque = MessageConstant.pushCtxMap.get(userId);
        if (deque == null) {
            deque = new ConcurrentLinkedDeque<>();
            MessageConstant.pushCtxMap.put(userId, deque);
        }
        deque.add(ctx);
    }

    /**
     * 响应结果
     * @param ctx
     * @param req
     * @param res
     */
    public static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse res) {
        // 返回应答给客户端
        if (res.status().code() != HttpResponseStatus.OK.code()) {
            ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
        }

        // 如果是非Keep-Alive，关闭连接
        ChannelFuture f = ctx.channel().writeAndFlush(res);
        if (!isKeepAlive(req) || res.status().code() != HttpResponseStatus.OK.code()) {
            f.addListener(ChannelFutureListener.CLOSE);
        }

    }

    private static boolean isKeepAlive(FullHttpRequest req) {
        return false;
    }


    /**
     * 异常处理，netty默认是关闭channel
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        // TODO Auto-generated method stub
        //输出日志
        cause.printStackTrace();
        ctx.close();
    }

    /**
     * netty心跳
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.READER_IDLE) {
                ctx.channel().close();
                System.out.println("连接超时，将关闭链路");
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }
}

